package com.opennews.openplatform.myspringbootcore.util

import java.time.LocalDateTime
import java.time.YearMonth
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.*

object DateHelper {
    const val LONG_DATE: String = "yyyy-MM-dd"
    const val LONG_DATE_TIME: String = "yyyy-MM-dd HH:mm:ss"
    const val LONG_TIMESTAMP: String = "yyyyMMddHHmmSSS"
    const val UTC_DATE_TIME: String = "yyyy-MM-dd'T'HH:mm"
    const val UTC_LONG_DATE_TIME: String = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"

    // yyyy-MM-dd'T'HH:mm:ss'Z'
    private val UTC_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern(UTC_LONG_DATE_TIME)

    /**
     * Gets current system date.
     *
     * @return The LocalDateTime instance of current date.
     */
    fun getCurrentLocalDateTime(): LocalDateTime = LocalDateTime.now()

    fun convertLocalDateTimeToUTCString(dateTime: LocalDateTime): String {
        // Gets the default time zone.
        val defaultTimeZone = TimeZone.getDefault()

        // Converts TimeZone to ZoneId.
        val zoneId = defaultTimeZone.toZoneId()

        // Converts LocalDateTime in the default time zone ZonedDateTime
        val localDateTimeInDefaultZone = dateTime.atZone(zoneId)

        // Converts the ZonedDateTime to UTC.
        val utcDateTime = localDateTimeInDefaultZone.withZoneSameInstant(ZoneId.of("UTC"))

        // Formats the UTC ZonedDateTime to the desired UTC format.
        return utcDateTime.format(UTC_FORMATTER)
    }

    fun convertUTCStringToLocalDateTime(dateStr: String): LocalDateTime {
        // Parses the UTC string into a ZonedDateTime object (assuming UTC)
        val utcDateTime = ZonedDateTime.parse(dateStr.trim())

        // Converts UTC ZonedDateTime to the system's default time zone.
        val defaultZoneId = ZoneId.systemDefault()
        val localDateTimeInDefaultZone = utcDateTime.withZoneSameInstant(defaultZoneId)

        // Convert ZonedDateTime (in default zone) to LocalDateTime (without time zone).
        return localDateTimeInDefaultZone.toLocalDateTime()
    }

    fun format(dateTime: LocalDateTime, format: String): String {
        return dateTime.format(DateTimeFormatter.ofPattern(format))
    }

    /**
     * Parses the input string to LocalDateTime with a specific format.
     *
     * @param dateStr    The string containing the date value.
     * @param dateFormat The date format.
     * @return The LocalDateTime instance.
     */
    fun parseToLocalDateTime(dateStr: String, dateFormat: String): LocalDateTime {
        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(dateFormat))
    }

    /**
     * Parses the input string to LocalDateTime with specific format.
     *
     * @param dateStr The string containing the date value.
     * @return The LocalDateTime instance.
     */
    fun parseFromLongDateTime(dateStr: String): LocalDateTime {
        return if (dateStr.contains("T") || dateStr.endsWith("Z")) {
            convertUTCStringToLocalDateTime(dateStr)
        } else {
            parseToLocalDateTime(dateStr, LONG_DATE_TIME)
        }
    }

    /**
     * Gets the first day of the month based on the provided date.
     * Example: If date is 2019-12-13, the return value is 2019-12-01.
     *
     * @param date Any day in a month.
     * @return The first day of the month.
     */
    fun getFirstDayOfMonth(date: LocalDateTime): LocalDateTime {
        return date.withDayOfMonth(1)
    }

    /**
     * Gets the first day of the month based on the provided year and month.
     *
     * @param year  The year.
     * @param month The month.
     * @return The first day of the month in the year.
     */
    fun getFirstDayOfMonth(year: Int, month: Int): LocalDateTime {
        return LocalDateTime.of(year, month, 1, 0, 0)
    }

    /**
     * Gets the last day of the month based on the provided year and month.
     *
     * @param year  The year.
     * @param month The month.
     * @return The last day of the month in the year.
     */
    fun getLastDayOfMonth(year: Int, month: Int): LocalDateTime {
        return getFirstDayOfMonth(year, month).withDayOfMonth(YearMonth.of(year, month).lengthOfMonth())
    }

    /**
     * Gets the first day of the next month based on the provided year and month.
     *
     * @param year  The year.
     * @param month The month.
     * @return The first day of the next month in the year.
     */
    fun getFirstDayOfNextMonth(year: Int, month: Int): LocalDateTime {
        return getFirstDayOfMonth(year, month).plusMonths(1).withDayOfMonth(1)
    }
}